/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package dnbW.uiW.queryW.modelW;

import dnbW.dbW.PathExpression;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;

/**
 * Encapsulates the condition tree model. The data is stored in the user object
 * of each node.
 * @author zhouyong
 */
public class ConditionTreeModel extends DefaultTreeModel {

    public static final int UNSUPPORTED_NODE = 0;
    public static final int JOIN_NODE = 1;
    public static final int COMPARISON_NODE = 2;
    public static final int OPERATOR_NODE = 3;
    public static final int XCOMPARISON_NODE = 4;
    public static final int XJOIN_NODE = 5;

    // <editor-fold desc="Moving Node">
    private boolean movingNode = false;

    public boolean isMovingNode() {
        return movingNode;
    }
    // </editor-fold>

    // <editor-fold desc="Condition List">
    /**
     * List of leaf conditions in the condition tree
     */
    private List<DefaultMutableTreeNode> conditionList =
            new ArrayList<DefaultMutableTreeNode>();


    public List<DefaultMutableTreeNode> getConditionList() {
        return Collections.unmodifiableList(conditionList);
    }
    // </editor-fold>

    // <editor-fold desc="Constructors">
    /**
     * Creates a condition tree model with the default AND operator as root
     */
    public ConditionTreeModel() {
        super(ConditionNodeFactory.createOperatorNode(Operators.AND_OP));
    }

    /**
     * Creates a condition tree model with the given operator as root
     * @param rootOperator
     */
    public ConditionTreeModel(String rootOperator) {
        super(ConditionNodeFactory.createOperatorNode(rootOperator));
    }
    // </editor-fold>

    private void addNode(DefaultMutableTreeNode node, DefaultMutableTreeNode parent) {
        // If no parent is given, the node is added to the root node
        if (parent == null) {
            parent = this.getConditionRoot();
        }
        // Add a condition into the condition tree
        parent.add(node);
        // Add a condition into the condition list
        if (isConditionLeaf(node)) {
            conditionList.add(node);
        }

        int index = parent.getIndex(node);
        super.nodesWereInserted(parent, new int[]{index});
    }

    public static boolean isConditionLeaf(DefaultMutableTreeNode node) {
        int nodeType = getNodeType(node);
        if (nodeType == COMPARISON_NODE || nodeType == JOIN_NODE || nodeType == XCOMPARISON_NODE) {
            return true;
        }
        return false;
    }

    public static int getNodeType(DefaultMutableTreeNode node) {
        Object userObject = node.getUserObject();

        // check operator node
        if (userObject instanceof OperatorNodeObject) {
            return OPERATOR_NODE;
        }

        if (userObject instanceof XComparisonNodeObject) {
            return XCOMPARISON_NODE;
        }

        if (userObject instanceof ComparisonNodeObject) {
            return COMPARISON_NODE;
        }

        if (userObject instanceof JoinNodeObject) {
            return JOIN_NODE;
        }

        return UNSUPPORTED_NODE;
    }

    /**
     * Deletes a node from its parent. If retainChildren is true, the children of the deleted node
     * will become children of its parent. Otherwise, the whole subtree are deleted.
     * @param node
     * @param retainChildren
     */
    public void deleteNode(DefaultMutableTreeNode node, boolean retainChildren) {
        if (node == this.getConditionRoot()) {
            return;
        }

        if (ConditionTreeModel.isConditionLeaf(node)) {
            System.out.println("Node: " + node);
            // remove from condition list
            conditionList.remove(node);
            // remove from parent
            TreeNode parent = node.getParent();
            int index = parent.getIndex(node);
            node.removeFromParent();
            super.nodesWereRemoved(parent, new int[]{index}, new Object[]{node});
        } else {
            DefaultMutableTreeNode parent = (DefaultMutableTreeNode) node.getParent();
            // Do client want to retain descendants of deleted node?
            if (retainChildren) {
                // Yes. The children of deleted node become children of the deleted node's parent
                for (Enumeration em = node.children(); em.hasMoreElements();) {
                    moveNode((DefaultMutableTreeNode) em.nextElement(), parent);
                }
            } else {
                // No. Delete the whole subtree (recursively)
                for (Enumeration em = node.children(); em.hasMoreElements();) {
                    deleteNode((DefaultMutableTreeNode) em.nextElement(), false);
                }
            }
            // delete
            deleteNode(node, retainChildren);
        }

    }

    /**
     * Deletes a node from its parent. 
     * @param node
     */
    public void deleteLonelyNode(DefaultMutableTreeNode node) {
        if (node == this.getConditionRoot()) {
            return;
        }

        System.out.println("Node: " + node);
        TreeNode parent = node.getParent();
        int index = parent.getIndex(node);
        node.removeFromParent();
        super.nodesWereRemoved(parent, new int[]{index}, new Object[]{node});
    }

    /**
     * Moves a node (and all of its subtree) to become a child of a new parent
     * @param node      The moved node
     * @param newParent The moved node's new parent
     */
    public void moveNode(DefaultMutableTreeNode node, DefaultMutableTreeNode newParent) {
        // If the new parent node is currently the parent of the moved node, nothing to do
        if (newParent.isNodeChild(node)) {
            return;
        }

        // remove node from its parent
        TreeNode parent = node.getParent();
        int index = parent.getIndex(node);
        node.removeFromParent();

        // set isMovingNode parameter to true, user must check isMovingNode() (for mutual lock??)
        movingNode = true;
        nodesWereRemoved(parent, new int[]{index}, new Object[]{node});
        addNode(node, newParent);
        movingNode = false;
    }

    /**
     * Breaks the connection between a condition leaf node and its parent by introducing 
     * an AND operator between them.
     * @param leafNode  The leaf node
     * @return          The added in-between operator node
     */
    protected DefaultMutableTreeNode breakLeafNode(DefaultMutableTreeNode leafNode) {
        if (!ConditionTreeModel.isConditionLeaf(leafNode)) {
            return null;
        }

        DefaultMutableTreeNode parent = (DefaultMutableTreeNode) leafNode.getParent();
        DefaultMutableTreeNode newOpNode =
                ConditionNodeFactory.createOperatorNode(Operators.AND_OP);
        this.addNode(newOpNode, parent);
        this.moveNode(leafNode, newOpNode);
        return newOpNode;
    }

    public DefaultMutableTreeNode addComparisonNode(PathExpression pathExpression,
            String operator, String value, DefaultMutableTreeNode parent, boolean allowMerging) {

        if (parent == null) {
            parent = this.getConditionRoot();
        } else {
            if (isConditionLeaf(parent)) {
                parent = this.breakLeafNode(parent);
            }
        }

        DefaultMutableTreeNode node =
                ConditionNodeFactory.createComparisonNode(pathExpression, operator, value);
        ((ConditionNodeLeafObject)node.getUserObject()).setAllowMerging(allowMerging);
        // Add a condition into the condition tree
        this.addNode(node, parent);
        return node;
    }

    public DefaultMutableTreeNode addXComparisonNode(PathExpression pathExpression,
            String operator, String value, DefaultMutableTreeNode parent, boolean allowMerging) {

        if (parent == null) {
            parent = this.getConditionRoot();
        } else {
            if (isConditionLeaf(parent)) {
                parent = this.breakLeafNode(parent);
            }
        }

        DefaultMutableTreeNode node =
                ConditionNodeFactory.createXComparisonNode(pathExpression, operator, value);
        ((ConditionNodeLeafObject)node.getUserObject()).setAllowMerging(allowMerging);

        this.addNode(node, parent);
        return node;
    }
    
    public DefaultMutableTreeNode addXComparisonNode(
            PathExpression pathExpression,
            String operator,
            String value,
            DefaultMutableTreeNode parent, JoinNodeObject joinBridge) {

        if (parent == null) {
            parent = this.getConditionRoot();
        } else {
            if (isConditionLeaf(parent)) {
                parent = this.breakLeafNode(parent);
            }
        }

        DefaultMutableTreeNode node =
                ConditionNodeFactory.createXComparisonNode(pathExpression, operator, value);
        
        this.addNode(node, parent);
        ((XComparisonNodeObject) node.getUserObject()).setJoinBridge(joinBridge);
        return node;
    }

    /**
     * Adds a new join node with given left, right path and operator to the given parent node. 
     * Note that if the parent is a condition, an in-between AND node will be introduced 
     * between that node and the added node.
     * If no parent is given, the condition is added into the root node.
     * @param operator
     * @param parent
     */
    public DefaultMutableTreeNode addJoinNode(PathExpression leftPath, PathExpression rightPath,
            String operator, DefaultMutableTreeNode parent) {
        if (parent == null) {
            parent = this.getConditionRoot();
        } else {
            if (isConditionLeaf(parent)) {
                parent = this.breakLeafNode(parent);
            }
        }

        DefaultMutableTreeNode node =
                ConditionNodeFactory.createJoinNode(leftPath, rightPath, operator);

        this.addNode(node, parent);
        return node;
    }

    /**
     * Adds a new operator node to the given parent node. Note that if the parent is a condition,
     * an in-between AND node will be introduced between that node and the added node.
     * If no parent is given, the condition is added into the root node.
     * @param operator
     * @param parent
     */
    public void addOperatorNode(String operator, DefaultMutableTreeNode parent) {
        if (parent == null) {
            parent = this.getConditionRoot();
        } else {
            if (isConditionLeaf(parent)) {
                parent = this.breakLeafNode(parent);
            }
        }

        DefaultMutableTreeNode node = ConditionNodeFactory.createOperatorNode(operator);
        addNode(node, parent);
    }

    /**
     * Gets the root of the condition tree
     * @return
     */
    public DefaultMutableTreeNode getConditionRoot() {
        return (DefaultMutableTreeNode) this.getRoot();
    }
}
